using System;
using System.Diagnostics;
using System.Threading;
using System.Windows.Forms;
using JK.EventRouter.Threading;

namespace JK.EventRouter.Eventing
{
    /// <summary>Provide a mechanism to marshall invocations of <see cref="Action{T}"/> onto the GUI thread</summary>
    /// <remarks>Accepts an argument of type T</remarks>
    public class GuiThreadAction<T> : IAction<T>
    {
        private readonly Action<T> _mySafeAction;
        private readonly Control _control;
        private Exception _crossThreadException;

        public GuiThreadAction(Action<T> myAction, Control control)
        {
            _control = control;
            _mySafeAction = SafeExecute(myAction);
        }

        public void Act(T item)
        {
            if(_control.InvokeRequired)
            {
                //synchronous call to invoke the delegate on another thread
                _control.Invoke(_mySafeAction, item); 
                //did any exceptions occur on the other thread?
                HandleCrossThreadExceptions();
            }
            else
            {
                _mySafeAction(item);
            }
        }

        private void HandleCrossThreadExceptions()
        {
            if(_crossThreadException!= null)
            {
                //raise this specific exception so the EventRouter can raise the original
                //exception with a correct stack trace
                CrossThreadException cte = new CrossThreadException(_crossThreadException);
                throw cte;
            }
        }

        /// <summary>
        /// decorate our Action delegate with a try/catch wrapper that sets our local
        /// exception field
        /// </summary>
        /// <param name="myAction"></param>
        /// <returns></returns>
        private Action<T> SafeExecute(Action<T> myAction)
        {
            return item =>
                       {
                           try
                           {
                               myAction(item);
                           }
                           catch (Exception e)
                           {
                               _crossThreadException = e;
                           }
                       };
        }
    }

    /// <summary>Provide a mechanism to marshall invocations of <see cref="Act"/> onto the GUI thread</summary>
    /// <remarks>An argument-less signature</remarks>
    public class GuiThreadAction : IAction
    {
        private readonly Act _myAction;
        private readonly Control _control;
        private Exception _crossThreadException;

        public GuiThreadAction(Act myAction, Control control)
        {
            _control = control;
            _myAction = SafeExecute(myAction);
        }

        public void Act()
        {
            if (_control.InvokeRequired)
            {
                _control.Invoke(_myAction);
                //did any exceptions occur on the other thread?
                HandleCrossThreadExceptions();
            }
            else
            {
                _myAction();
            }
        }

        private Act SafeExecute(Act action)
        {
            return () =>
                       {
                           try
                           {
                               action();
                           }
                           catch (Exception e)
                           {
                               _crossThreadException = e;
                           }
                       };
        }

        private void HandleCrossThreadExceptions()
        {
            if (_crossThreadException != null)
            {
                //raise this specific exception so the EventRouter can raise the original
                //exception with a correct stack trace
                CrossThreadException cte = new CrossThreadException(_crossThreadException);
                throw cte;
            }
        }

    }
}